package com.lanlion.portlet.tool;

import com.lanlion.porlet.common.CommonEntityFileAnnotation;
import com.lanlion.porlet.common.WechatPayEntity;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.cert.X509Certificate;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.*;

/**
 * Created by zhangyu5-iri on 2017/7/25.
 */
public class WechatEncryptHelper {

    static Logger logger = LoggerFactory.getLogger(WechatEncryptHelper.class);

    private static final String CIPHER_PROVIDER = "SunJCE";
    private static final String TRANSFORMATION_PKCS1Paddiing = "RSA/ECB/PKCS1Padding";

    private static final String CHAR_ENCODING = "UTF-8";//固定值，无须修改

    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;


    /**
     * 对Map参数对进行字典顺序进行MD5加密
     * @param params 需要加密的map
     * @param secret 秘钥
     * @return
     */
    public static String getSignByMD5(Map<String,String> params , String secret){

        SortedMap<String, String> sortedMap = new TreeMap<String, String>(params);

        // 将参数拼接成字符串
        StringBuilder toSign = new StringBuilder();
        for (String key : sortedMap.keySet()) {
            String value = params.get(key);
            if (StringUtils.isNotEmpty(value) && !"sign".equals(key) && !"key".equals(key)) {
                toSign.append(key).append("=").append(value).append("&");
            }
        }
        toSign.append("key=").append(secret);
        if (toSign.toString() != null) {
            try {
                // 生成一个MD5加密计算摘要
                MessageDigest md = MessageDigest.getInstance("MD5");

                // 计算md5函数
                logger.info("MD5签名字符串："+toSign.toString());
                md.update(toSign.toString().getBytes("UTF-8"));

                return new BigInteger(1, md.digest()).toString(16);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * SHA1 加密
     * @param sourceStr 待加密字符串
     * @return 加密字符串
     * @throws DigestException
     */
    public static String getSignBySHA1(String sourceStr) throws DigestException {
        try {
            //指定sha1算法
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(sourceStr.getBytes());
            //获取字节数组
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            logger.info("SHA1签名字符串："+hexString.toString());
            return hexString.toString().toLowerCase();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new DigestException("加密错误！");
        }
    }




    /**
     * 微信证书HMAC-SHA256签名
     *
     * @param params
     * @param secret
     * @return
     */
    public  static String getSignBySHA256(Map<String, String> params, String secret) {
        // 需要保证排序
        SortedMap<String, String> sortedMap = new TreeMap<String, String>(params);
        // 将参数拼接成字符串
        StringBuilder toSign = new StringBuilder();
        for (String key : sortedMap.keySet()) {
            String value = params.get(key);
            if (StringUtils.isNotEmpty(value) && !"sign".equals(key) && !"key".equals(key)) {
                toSign.append(key).append("=").append(value).append("&");
            }
        }

        toSign.append("key=").append(secret);
        logger.info("SHA256签名字符串："+toSign.toString());
        return sha256_HMAC(toSign.toString(), secret);
    }

    /**
     * 加密HMAC-SHA256
     *
     * @param message
     * @param secret
     * @return
     */
    private static String sha256_HMAC(String message, String secret) {
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            byte[] bytes = sha256_HMAC.doFinal(message.getBytes("UTF-8"));
            String sign = byteArrayToHexString(bytes);
            sign = sign.toUpperCase();
            return sign;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密后的字节转字符串
     *
     * @param b
     * @return
     */
    private static String byteArrayToHexString(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp = null;
        for (int n = 0; b != null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }




    //数据加密方法
    private static byte[] encryptPkcs1padding(PublicKey publicKey, byte[] data) throws Exception {
        Cipher ci = Cipher.getInstance(TRANSFORMATION_PKCS1Paddiing, CIPHER_PROVIDER);
        ci.init(Cipher.ENCRYPT_MODE, publicKey);
        return ci.doFinal(data);
    }
    //加密后的秘文，使用base64编码方法
    private static String encodeBase64(byte[] bytes) throws Exception {
        return Base64.getEncoder().encodeToString(bytes);
    }
    /**
     *  对敏感内容（入参Content）加密
     *  path 为平台序列号接口解密后的密钥 pem 路径
     */
    public static String rsaEncrypt(String Content, String cert) throws Exception {
        final byte[] PublicKeyBytes = cert.getBytes();
        X509Certificate certificate = X509Certificate.getInstance(PublicKeyBytes);
        PublicKey publicKey = certificate.getPublicKey();

        return encodeBase64(encryptPkcs1padding(publicKey, Content.getBytes(CHAR_ENCODING)));
    }

    /**
     * 微信关键字解密
     * @param Content 需要解密的呢绒
     * @param certStr 证书字符串
     * @return
     * @throws Exception
     */
    public static String rsaEncryptByCert(String Content, String certStr) throws Exception {
        X509Certificate certificate = X509Certificate.getInstance(certStr.getBytes());
        PublicKey publicKey = certificate.getPublicKey();
        return encodeBase64(encryptPkcs1padding(publicKey, Content.getBytes(CHAR_ENCODING)));
    }


    public String decryptCertSN(String associatedData, String nonce, String cipherText, String apiv3Key) throws Exception{
        final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
        SecretKeySpec key = new SecretKeySpec(apiv3Key.getBytes(), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        cipher.updateAAD(associatedData.getBytes());
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }


    /**
     *
     * @param aad
     * @param iv
     * @param cipherText
     * @param apiKey
     * @return
     */
    public static String aesgcmDecrypt(String aad, String iv, String cipherText,String apiKey){
        try {
            final Cipher cipher = Cipher.getInstance(ALGORITHM, "SunJCE");
            SecretKeySpec key = new SecretKeySpec(apiKey.getBytes(), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(aad.getBytes());
            return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
            return "fail";
        }
    }

    /**
     * 将带CommonEntityFileAnnotation的实体进行签名
     * @param obj 需要加密的实体类
     * @param key 支付秘钥
     * @return
     * @throws Exception
     */
    public static Map getSignFromEntity(Object obj,String key) throws Exception {

        Class clz = obj.getClass();
        boolean isAnnotation = clz.isAnnotationPresent(CommonEntityFileAnnotation.class);
        String sign = null ;
        String signType = null;
        // 需要保证排序
        SortedMap<String, String> sortedMap = null;
        if(isAnnotation){
            sortedMap = new TreeMap<String, String>();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields){

                boolean isField4Annotation = field.isAnnotationPresent(CommonEntityFileAnnotation.class);
                if(true){
                    CommonEntityFileAnnotation fileAnnotation = field.getAnnotation(CommonEntityFileAnnotation.class);
                    field.setAccessible(true);

                    //获取加密类型，用户自动正确加密
                    if(null!=field.get(obj)&&!"".equals(field.get(obj).toString()) &&!"0".equals(field.get(obj).toString())){
                        if(field.getName().equals("signType")){
                            signType = field.get(obj).toString();
                        }
                        sortedMap.put(fileAnnotation.name(),field.get(obj).toString());
                    }

                }
            }

            if(null!=signType&&!signType.equals("")){
                if(signType.equals(WechatConstants.HMACSHA256)){
                    sign = getSignBySHA256(sortedMap,key);
                }else if(signType.equals(WechatConstants.MD5)||null == signType){
                    sign = getSignByMD5(sortedMap,key);
                }

                sortedMap.put("sign",sign);
            }

        }else {
            throw new Exception("参数类型未使用CommonEntityFileAnnotation注解标注");
        }

        return sortedMap;
    }


    public static void main(String[] args) {
        WechatPayEntity payEntity = new WechatPayEntity();
        payEntity.setAppid("ddwrewr");
        payEntity.setProductId("P003434");
        payEntity.setNotifyUrl("http://www.baidu.com");
        payEntity.setSignType("MHDJD");
        payEntity.setSignType(WechatConstants.HMACSHA256);
        try {
            System.out.println(getSignFromEntity(payEntity,"dddddd"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
